//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Copyright 2012 Microsoft Corporation. All Rights Reserved
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.   
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

namespace Preflight.Security
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Security.Cryptography;
    using System.Text;

    internal class SecureTokenValidator : ISecureTokenValidator
    {
        public static readonly TimeSpan DefaultTokenValidity = TimeSpan.FromSeconds(30);
        private readonly X509EncryptDecrypt encryptor;
        private readonly TokenFormatter tokenFormatter;
        private readonly TimeSpan tokenValidity;

        public SecureTokenValidator(X509EncryptDecrypt encryptor, TokenFormatter tokenFormatter) 
            : this(encryptor, tokenFormatter, DefaultTokenValidity)
        {
        }

        public SecureTokenValidator(X509EncryptDecrypt encryptor, TokenFormatter tokenFormatter, TimeSpan tokenValidity)
        {
            this.encryptor = encryptor;
            this.tokenFormatter = tokenFormatter;
            this.tokenValidity = tokenValidity;
        }

        public bool IsTokenValid(string base64Token, string resource)
        {
            if (string.IsNullOrWhiteSpace(base64Token))
            {
                Log.LogVerbose("Token is null or empty");
                return false;
            }

            try
            {
                Log.LogVerbose("Converting from base 64");
                var encryptedToken = Convert.FromBase64String(base64Token);

                Log.LogVerbose("Decrypting...");
                var decryptedTokenBytes = this.encryptor.Decrypt(encryptedToken);

                Log.LogVerbose("UTF 8 formatted string");
                var rawToken = Encoding.UTF8.GetString(decryptedTokenBytes);

                Log.LogVerbose("Deconstructing raw token");
                var values = this.tokenFormatter.Parse(rawToken);

                var hasAllKeys = this.HasAllKeys(values);
                var resourceNameValid = this.IsResourceNameValid(values, resource);
                var isTokenStillValid = this.IsTokenStillValid(values);

                Log.LogVerbose("Token Has All Keys: {0} | Resource Name Valid: {1} | Token Has Not Expired: {2}", hasAllKeys, resourceNameValid, isTokenStillValid);

                return
                    hasAllKeys &&
                    resourceNameValid &&
                    isTokenStillValid;
            }
            catch (CryptographicException exception)
            {
                Log.LogVerbose("Token is not valid. Failed to decrypt. Exception: {0}", exception);
                return false;
            }
            catch (FormatException exception)
            {
                Log.LogVerbose("Token is invalid: Invalid data: {0}", exception);
                return false;
            }
        }

        private bool HasAllKeys(Dictionary<string, string> values)
        {
            return 
                values.ContainsKey(TokenKeys.IssueDate) && 
                values.ContainsKey(TokenKeys.ResourceName);
        }

        private bool IsTokenStillValid(IDictionary<string, string> values)
        {
            var issueDateValue = values[TokenKeys.IssueDate];

            try
            {
                var issueDate = DateTime.Parse(issueDateValue, CultureInfo.InvariantCulture).ToUniversalTime();
                return DateTime.UtcNow.Subtract(issueDate) <= this.tokenValidity;
            }
            catch (FormatException)
            {
                Log.LogVerbose("Invalid Token Issue Date: {0}", issueDateValue);
                return false;
            }
        }

        private bool IsResourceNameValid(Dictionary<string, string> values, string resource)
        {
            return string.Compare(values[TokenKeys.ResourceName], resource, true, CultureInfo.InvariantCulture) == 0;
        }
    }
}
